package mdc

import (
	"context"
	"encoding/binary"
	"go.uber.org/zap"
	"sync"
	"time"
	"wzgames/internal/client"
	"wzgames/internal/codec/sfs"
	"wzgames/internal/constant"
	"wzgames/internal/gloader"
	"wzgames/internal/gvalidator"
	"wzgames/internal/model/login"
	"wzgames/internal/response"
	"wzgames/pkg/actor/actor"
	"wzgames/pkg/actor/actorservice"
	"wzgames/pkg/common/version"
	"wzgames/pkg/logger"
	"wzgames/pkg/server/ws"
)

// 玩家长连接消息处理

var (
	receiver *WsReceiver
	once     sync.Once
)

type WsReceiver struct {
	pingPong *sfs.RawPacket // ping pong 包

	ppid *actor.PID // 游戏节点Actor 如 aviator g8 roubig等
}

func NewWsReceiver(ppid *actor.PID) *WsReceiver {
	if receiver == nil {
		once.Do(func() {
			receiver = &WsReceiver{
				ppid: ppid,
				pingPong: &sfs.RawPacket{
					Id:        constant.ActionPingPong,
					ControlId: constant.SysControlID,
					Content:   make([]byte, 2),
				},
			}
			binary.BigEndian.PutUint16(receiver.pingPong.Content, 0)
		})
	}

	return receiver
}

func GetReceiver() *WsReceiver {
	return receiver
}

func (mr *WsReceiver) Receive(evt ws.Event) {
	defer logger.Recover()

	switch m := evt.Message().(type) {
	case *sfs.TSfsObject:
		if m.GetControlID() == constant.SysControlID && m.GetID() == constant.ActionPingPong { // ping pong
			evt.Session().Send(mr.pingPong)
			actorservice.Send(evt.Session().LinkPid(), constant.PingPong{})
			return
		}
		switch m.GetControlID() {
		case constant.SysControlID: // handshake login命令
			actorservice.Send(mr.ppid, evt)
		case constant.ExControlID: // 登录后的客户端消息 发往对应的Player actor
			actorservice.Send(evt.Session().LinkPid(), evt)
		default:
			logger.Warn("[WSC] illegal control id", zap.Uint8("ControlID", m.GetControlID()))
		}
	case ws.SessionAccepted:
	case ws.SessionClosed: // WebSocket断开
		actorservice.Send(evt.Session().LinkPid(), constant.WSClosed{
			Handle: evt.Session().LinkID(),
			SessID: evt.Session().ID(),
			C:      m.C,
		})
	default:
		logger.Error("[WSC] unknown client message type")
	}
}

func (mr *WsReceiver) HandleHandShake(sess ws.ISession, data *sfs.TSfsObject) {
	m := &sfs.RawPacket{
		Id:        constant.ActionHandShake,
		ControlId: constant.SysControlID,
	}
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteIntS(sfs.KeyCompressThreshold, int32(sfs.CompressionThreshold))
	xw.WriteIntS(sfs.KeyMaxMessageSize, int32(sfs.MaxMessageSize))
	//xw.WriteStringS(sfs.KeySessionToken, sess.Token())
	xw.WriteTo(m)

	sess.Send(m)
}

// HandleLogin 客户端WebSocket登录
func (mr *WsReceiver) HandleLogin(sess ws.ISession, data *sfs.TSfsObject) {
	content := data.GetContent()
	if content == nil {
		mr.GenLoginErr(sess, response.ErrBadRequest)
		return
	}
	zn := content.GetUtfString(sfs.KeyZoneName) // 游戏实例编码
	un := content.GetUtfString(sfs.KeyUserName)
	if zn == "" || un == "" || !gvalidator.IsAlphaNumeric(zn) || len(zn) > 15 ||
		!gvalidator.IsAgentUname(un) || len(un) > 64 {
		mr.GenLoginErr(sess, response.ErrBadRequest)
		return
	}
	paramObj := content.GetSFSObject(sfs.ParamId)
	if paramObj == nil {
		mr.GenLoginErr(sess, response.ErrBadRequest)
		return
	}
	token := paramObj.GetUtfString(sfs.KeyToken)
	sessToken := paramObj.GetUtfString(sfs.KeySessToken)
	curr := paramObj.GetUtfString(sfs.KeyCurrency)
	lang := paramObj.GetUtfString(sfs.KeyLang)
	ver := paramObj.GetUtfString(sfs.KeyVersion)
	platform := paramObj.GetSFSObject(sfs.KeyPlatform)
	if len(token) != 32 || len(sessToken) > 80 || len(sessToken) < 32 || platform == nil ||
		!gvalidator.IsAlphaNumeric(curr) || len(curr) > 10 || !gvalidator.IsAlpha(lang) || len(lang) > 10 ||
		!gvalidator.IsAlphaNumeric(token) || !gvalidator.IsAlphaNumeric(sessToken) || !version.ValidSimpleVersionFormat(ver) {
		mr.GenLoginErr(sess, response.ErrBadRequest)
		return
	}
	deviceType := platform.GetUtfString(sfs.KeyDeviceType)
	deviceInfo := platform.GetUtfString(sfs.KeyDeviceInfo)
	userAgent := platform.GetUtfString(sfs.KeyUserAgent)
	if !gvalidator.IsAvailableDevice(deviceType) || len(deviceInfo) > 1000 || len(userAgent) > 1000 {
		mr.GenLoginErr(sess, response.ErrBadRequest)
		return
	}
	// 游戏实例编码 解码 转为对应的数字实例ID
	instId, err := gvalidator.DecodeGameInstID(zn)
	if err != nil {
		logger.Error("err == ", zap.String("error", err.Error()))
		mr.GenLoginErr(sess, response.ErrBadRequest)
		return
	}
	// 根据登录的币种 返回对应的系统游戏ID、币种方案ID
	gameId, gcId, code := gloader.GameMgr().CheckGameInst(instId, curr)
	if code != 0 {
		mr.GenLoginErr(sess, code)
		return
	}

	req := &login.WsLogin{
		GameInstId:   instId,
		GameID:       gameId,
		Un:           un,
		Token:        token,
		Currency:     curr,
		Lang:         lang,
		SessionToken: sessToken,
		Version:      ver,
		DeviceType:   deviceType,
		DeviceInfo:   deviceInfo,
		UserAgent:    userAgent,
		LoginNodeId:  gloader.BootConfig().Node().NodeId,
		ClientIP:     sess.RemoteIP(),
	}
	// 登录验证
	reply := &login.WsLoginReply{}
	rpcctx, cancelFn := context.WithTimeout(context.Background(), 15*time.Second)
	err = client.GetCatClient().Call(rpcctx, "WsLoginAuth", req, reply)
	cancelFn()
	if err != nil {
		logger.Error("[WSC] ws login fail", zap.Error(err))
		mr.GenLoginErr(sess, response.ErrInternal)
		return
	}
	if reply.Code != 0 {
		mr.GenLoginErr(sess, int16(reply.Code))
		return
	}
	if reply.GcId == 0 {
		reply.GcId = gcId
	}
	// 认证成功后发往游戏节点进行玩家登录
	actorservice.Send(mr.ppid, &login.LoginGame{
		GameInstId: instId,
		Reply:      reply,
		Currency:   curr,
		Lang:       lang,
		SessID:     sess.ID(),
	})
}

func (mr *WsReceiver) GenLoginErr(sess ws.ISession, code int16) {
	if sess == nil {
		return
	}
	pkg := &sfs.RawPacket{ControlId: constant.SysControlID, Id: constant.ActionLogin}
	xw := sfs.XWriter{}
	defer xw.Reset()
	xw.WriteUShort(uint16(2))
	xw.WriteShortS(sfs.KeyErrorCode, code)
	xw.WriteUtfStringArray(sfs.KeyErrorParams, []string{})

	xw.WriteTo(pkg)
	sess.Send(pkg)
	sess.Acceptor().Server().RemoveSession(sess)
}
